package com.haltakov.letterjumble.ai;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import com.haltakov.letterjumble.game.api.Board;
import com.haltakov.letterjumble.game.api.BoardWord;
import com.haltakov.letterjumble.game.api.ComputerPlayer;
import com.haltakov.letterjumble.game.api.Letter;
import com.haltakov.letterjumble.game.api.WordDirection;

public class ScrabbleAI implements Runnable {
	private static final int WORD_RANGE_MULTIPLIER = 3;	
	private static final boolean DEBUG = true;
	
	private ArrayList<BoardWord> words = new ArrayList<BoardWord>();
	private TreeDictionary dictionary;
	private BoardWord foundWord;
	private ComputerPlayer player;
	private Board board; 
	
	private Object requestWordLock = new Object();
	
	private int currentX, currentY, prevWordId;
	private WordDirection currentDirection;
	
	private boolean alive = true;
	
	private int middleX, middleY;
	
	
	private Thread thread;
	
	public ScrabbleAI(TreeDictionary dictionary, ComputerPlayer player) {
		this.dictionary = dictionary;
		this.player = player;
		board = player.getGame().getBoard();
		
		middleX = board.getCountX()/2;
		middleY = board.getCountX()/2;
		
		thread = new Thread(this, player.getName() + "Thread");
		thread.start();
	}
	
	
	private BoardWord extractField(List<Letter> field) {
		ArrayList<Letter> letters = new ArrayList<Letter>();
		
		boolean started = false;
		int first = -1;
		
		for (int i=0; i<field.size(); ++i) {
			Letter l = field.get(i);
			
			if (l != null) {
				if (!started) {
					started = true;
					first = i;
				}
				
				letters.add(l);
			}
			else if (started)
				break;				
		}
		
		if (currentDirection == WordDirection.HORIZONTAL)
			return new BoardWord(board, letters,   currentX+first, 	 currentY, 		   WordDirection.HORIZONTAL);
		else
			return new BoardWord(board, letters,   currentX, 		 currentY+first,   WordDirection.VERTICAL);
		
	}
	
	private void findWords(List<Letter> field, List<Letter> letters, int position, TreeDictionaryNodeIterator iter, boolean crossed) {
		boolean setCrossed = false;
		
		if (position < field.size() && field.get(position) != null) {
			if (field.get(position) == Letter.START_LETTER)
				setCrossed = true;
			else {
				if (iter.next(field.get(position).getCharacter())) {
					findWords(field, letters, position+1, iter, true);
					iter.prev();
				}		
		
				return;
			}
		}
		
		if (iter.isWord() && crossed)
			words.add(extractField(field));
		
		if (position >= field.size())
			return;
		
		
		for (int i=0; i<letters.size(); ++i) {
			Letter l = letters.get(i);
			if (l != null && iter.next(l.getCharacter())) {
				letters.set(i, null);
				field.set(position, l);
				
				findWords(field, letters, position+1, iter, crossed || setCrossed);
				
				iter.prev();					
				field.set(position, null);
				letters.set(i, l);
			}
		}
	}
	private void findWords(List<Letter> field, List<Letter> letters, int x, int y, WordDirection direction) {
		ArrayList<Letter> localLetters = new ArrayList<Letter>(letters.size());
		localLetters.addAll(letters);
		
		currentX = x;
		currentY = y;
		currentDirection = direction; 
		
		boolean breakOnNext = false;
		
		for (int i=0; i<field.size() && !breakOnNext; ++i) {
			if (field.get(i) != null)
				breakOnNext = true;
			
			findWords(field, localLetters, i, dictionary.nodesIterator(), false);				
		}
	}
	
	
	
	private void findWords() {
		System.out.println("Finding word...");
		
		words.clear();
		
		if (board.isEmpty()) {
			int side = player.getLetterCount()-1;
			int length = 2*side+1;
			
			ArrayList<Letter> field = new ArrayList<Letter>(length);
			for (int k=0; k<length; ++k)
				field.add(null);
			
			field.set(length/2+1, Letter.START_LETTER);

			findWords(field, player.getLetters(), Math.max(middleX-side, 0), Math.min(middleY+side, board.getCountX()-1), WordDirection.HORIZONTAL);
			findWords(field, player.getLetters(), Math.max(middleX-side, 0), Math.min(middleY+side, board.getCountY()-1), WordDirection.VERTICAL);
		}
		else {
		
			for (int i=0; i<board.getCountX(); ++i) {
				for (int j=0; j<board.getCountY(); ++j) {
					if (board.getLetter(i, j) != null) {
						
						// check vertical
						if ((j == 0					  || board.getLetter(i, j-1) == null) && 
							(j == board.getCountY()-1 || board.getLetter(i, j+1) == null)) {
							
							int top, bottom;
							
							for (top = j-1; top>=0; --top)
								if (board.getLetter(i, top) != null) {
									top += 1;
									assert top >= j;
									break;
								}
							top += 1;
							
							for (bottom = j+1; bottom<board.getCountY(); ++bottom)
								if (board.getLetter(i, bottom) != null) {
									bottom -= 1;
									assert bottom <= j;
									break;
								}
							bottom -= 1;
									
							top = Math.max(top, j-7);
							bottom = Math.min(bottom, j+7);
							
							int length = bottom-top;
							if (length > 1) {
								ArrayList<Letter> field = new ArrayList<Letter>(length);
								for (int k=0; k<length; ++k)
									field.add(null);
								
								if (DEBUG) System.out.println("Checking on COLUMN " + i + ": (" + top + "," + bottom + ")");
								field.set(j-top, board.getLetter(i, j));
								findWords(field, player.getLetters(), i, top, WordDirection.VERTICAL);
							}
						}
						
						// check horizontal
						if ((i == 0 				  || board.getLetter(i-1, j) == null) && 
							(i == board.getCountX()-1 || board.getLetter(i+1, j) == null)) {
							
							int left, right;
							
							for (left = i-1; left>=0; --left)
								if (board.getLetter(left, j) != null) {
									left += 1;
									assert left >= i;
									break;
								}
							left += 1;
							
							for (right = i+1; right<board.getCountX(); ++right)
								if (board.getLetter(right, j) != null) {
									right -= 1;
									assert right <= i;
									break;
								}
							right -= 1;
									
							left = Math.max(left, j-7);
							right = Math.min(right, j+7);
							
							int length = right-left;
							if (length > 1) {
								ArrayList<Letter> field = new ArrayList<Letter>(length);
								for (int k=0; k<length; ++k)
									field.add(null);
	
								if (DEBUG) System.out.println("Checking on ROW " + j + ": (" + left + "," + right + ")");
								field.set(j-left, board.getLetter(i, j));
								findWords(field, player.getLetters(), left, j, WordDirection.HORIZONTAL);
							}
						}
					}
				}
			}
		}

		Collections.sort(words);
		if (DEBUG) System.out.println(words.toString());
	}
	
	public BoardWord getWord() {
		
		synchronized (requestWordLock) {
			requestWordLock.notify();
			
			try {
				requestWordLock.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			prevWordId = -1;
			
			return getNextWord(); 
		}
	}

	@Override
	public void run() {
		while (alive) {
			synchronized (requestWordLock) {
				try {
					requestWordLock.wait();
				} catch (InterruptedException e) {}
				
				
				findWords();
				requestWordLock.notify();
			}
			
		}
	}


	public BoardWord getNextWord() {
		if (words.isEmpty())
			return null;
		
		if (prevWordId == -1) {
			int wordRange = Math.min(player.getDifficulty().getLevel()*WORD_RANGE_MULTIPLIER, words.size());
			
			prevWordId = words.size() - (int) (Math.random() * ((double) wordRange)) - 1;
		}
		else {
			--prevWordId;
			if (prevWordId < 0)
				return null;
		}
		
		if (DEBUG) System.out.println("Getting word number " + (words.size() - prevWordId));
		
		return words.get(prevWordId);		
	}
}
